package jfprincesstests.systemtests;

import java.util.Arrays;
import java.util.List;

import jfuzzyprincepless.agents.BaseNegotiationAgent;
import jfuzzyprincepless.agents.INegotiatingAgent;
import jfuzzyprincepless.zarchive.algorithm.structure.ICommandConstruction;
import jfuzzyprincepless.zarchive.algorithm.structure.ifcnstr.INegotiationCondition;
import jfuzzyprincepless.zarchive.algorithm.structure.ifcnstr.NegotiationAndCondition;
import jfuzzyprincepless.zarchive.algorithm.structure.ifcnstr.NegotiationIfConstruction;
import jfuzzyprincepless.negotiation.DefaultNegotiationContext;
import jfuzzyprincepless.negotiation.IExecutionContext;
import jfuzzyprincepless.negotiation.INegotiationContext;
import jfuzzyprincepless.negotiation.services.IService;
import laboratoryQ.junit.ignore.patch.BaseTestCase;

interface ICashInvestor extends IService {
	/**
	 * returns added value
	 */
	public double addToInvestPlan(double wantToAdd);
	public double getTotalToInvestmentPlan();

	public double getLastAddedToInvest();
	public double getLastWantedToInvest();
}

interface IBarterInvestor extends IService {
	/**
	 * returns added value
	 */
	public void addToInvestPlan(double wantToAdd);
	public double getTotalToInvestmentPlan();

	public void setCashIsInDificit(double investmentDificit);
	public void onNotNecessaryGoodsAppeared(double[] goodsValues);
	public boolean wantGoodsToSell();
}

public class TestInvestmentCompNegotiation extends BaseTestCase {
	private static final double EPS = 1e-5;
	private static final double COURSE = 10.0;
	private static final double BARTER_REGRESSION_COEF = 0.9;

	// TODOLONG: this test is expected to be GOOD_EXAMPLE
	/**
	 * ### ### ###  SCHEMA  ### ### ###
	 *
	 * 	IF (b0 AND b1 AND b2) THEN
	 * 		tact0()
	 * 		tact1()
	 * 	ELSE
	 * 		fact0()
	 *
	 * ### ### ### SEMANTIC ### ### ###
	 *
	 * 	IF (existsPercpectiveInvestment AND cacheIsNotAsMuchAsWant AND existNotNecessaryGoods) THEN
	 * 		barterNotNecessaryGoodsToInvestment()
	 * 		investCash()
	 * 	ELSE
	 * 		makeReportAboutNoActions()
	 *
	 */
	public void testInvestmentNegotiation() {
		TradeSystem tradeSystem = new TradeSystem();

		ExistsPercpectiveInvestment existsPercpectiveInvestment =
			new ExistsPercpectiveInvestment(0.9);
		INegotiationCondition cacheIsNotAsMuchAsWant =
			new CacheIsNotAsMuchAsWant(0.6);
		INegotiationCondition existNotNecessaryGoods =
			new ExistNotNecessaryGoods();
		INegotiationCondition andCondition =
			new NegotiationAndCondition(
				existsPercpectiveInvestment,
				cacheIsNotAsMuchAsWant,
				existNotNecessaryGoods
			);

		BarterNotNecessaryGoodsToInvestment barterCmd =
			new BarterNotNecessaryGoodsToInvestment();
		InvestCash investCashCmd =
			new InvestCash(700);

		NegotiationIfConstruction ifstruct =
			new NegotiationIfConstruction(
				andCondition,
				Arrays.<ICommandConstruction>asList(
					barterCmd,
					investCashCmd
				),
				Arrays.<ICommandConstruction>asList(
					new MakeReportCmd()
				)
			);
		ifstruct.extractAgentsFromConstructions();
		ifstruct.getAgents().add(tradeSystem);

		// ### ### ### ### ### RUN ### ### ### ### ###
		ifstruct.execute(new DefaultNegotiationContext());
		assertEquals(
			"TradeSystem: total investment = 79.0",
			tradeSystem.toString()
		);
	}

	private static interface ITradeSystem extends IService {
		public void investCash(double cash, double invGood);
		public void barterInvestGoods(
			double fromGoodVal,
			double toGoodVal
		);
	}

	private static class TradeSystem
		extends BaseNegotiationAgent
		implements ITradeSystem
	{
		private double totalInvested = 0.0;

		@Override
		public void investCash(double cash, double invGood) {
			if (cash / COURSE + EPS < invGood) {
				throw new IllegalArgumentException("Not enough cash");
			}
			totalInvested += invGood;
		}

		@Override
		public void barterInvestGoods(
			double fromGoodVal,
			double toGoodVal
		) {
			double fromGoodValEffective =
				fromGoodVal * BARTER_REGRESSION_COEF;
			if (fromGoodValEffective + EPS < toGoodVal) {
				throw new IllegalArgumentException(
					"Not enough fromGoodVal"
				);
			}
			totalInvested += toGoodVal;
		}

		@Override
		public String toString() {
			return "TradeSystem: total investment = " +
				totalInvested;
		}
	}

	// ### ### ### ### ### ### COMMANDS ## ### ### ### ### ### ###

	private static class BarterNotNecessaryGoodsToInvestment
		extends BaseNegotiationAgent
		implements ICommandConstruction, IBarterInvestor
	{
		private double wantToBarterOtherGoods = 0.0;
		private double wantToBarterOtherGoods_approvedByCINE = 0.0;
		private double toBarterPlan = 0.0;

		public BarterNotNecessaryGoodsToInvestment() {
		}

		@Override
		public void execute(IExecutionContext econtext) {
			double fromGoodVal = toBarterPlan;
			double toGoodVal =
				toBarterPlan * BARTER_REGRESSION_COEF;

			INegotiationContext ncontext =
				(INegotiationContext) econtext;
			ITradeSystem tradeSystem =
				ncontext.getService(ITradeSystem.class);
			tradeSystem.barterInvestGoods(
				fromGoodVal,
				toGoodVal
			);
		}

		@Override
		public void addToInvestPlan(double wantToAdd) {
			wantToBarterOtherGoods =
				wantToAdd / BARTER_REGRESSION_COEF;
		}

		@Override
		public double getTotalToInvestmentPlan() {
			return toBarterPlan * BARTER_REGRESSION_COEF;
		}

		@Override
		public void setCashIsInDificit(double investmentDificit) {
			investmentDificit /= BARTER_REGRESSION_COEF;
			wantToBarterOtherGoods_approvedByCINE =
				Math.min(wantToBarterOtherGoods, investmentDificit);
			wantToBarterOtherGoods = 0.0; // move to next ~~<<state>>
		}

		@Override
		public void onNotNecessaryGoodsAppeared(
			double[] goodsValues
		) {
			double totalAvailable = 0.0;
			for (double gv : goodsValues) {
				totalAvailable += gv;
			}

			double barterRequired =
				wantToBarterOtherGoods_approvedByCINE;
			double willBarter =
				Math.min(barterRequired, totalAvailable);

			double remaindBarter = willBarter;
			int n = goodsValues.length;
			for (int i = 0; i < n; i++) {
				double cur =
					Math.min(goodsValues[i], remaindBarter);
				goodsValues[i] -= cur;
				remaindBarter -= cur;
			}

			toBarterPlan += willBarter;
			wantToBarterOtherGoods_approvedByCINE -= willBarter;
		}

		@Override
		public boolean wantGoodsToSell() {
			return wantToBarterOtherGoods_approvedByCINE > EPS;
		}
	}

	private static class InvestCash
		extends BaseNegotiationAgent
		implements ICommandConstruction, ICashInvestor
	{
		private double cashAvailable;
		private double toInvestPlan = 0.0;
		private double lastWanted;
		private double lastAdded;

		public InvestCash(
			double cashAvailable
		) {
			this.cashAvailable = cashAvailable;
		}

		@Override
		public void execute(IExecutionContext econtext) {
			double investingCash = toInvestPlan * COURSE;

			INegotiationContext ncontext =
				(INegotiationContext) econtext;
			ITradeSystem tradeSystem =
				ncontext.getService(ITradeSystem.class);
			tradeSystem.investCash(investingCash, toInvestPlan);
		}

		@Override
		public double addToInvestPlan(double wantToAdd) {
			double cashForWantToAdd = wantToAdd * COURSE;
			double cashForWillAdd =
				Math.min(cashForWantToAdd, cashAvailable);
			double willAdd = cashForWillAdd / COURSE;

			toInvestPlan += willAdd;
			cashAvailable -= cashForWillAdd;

			lastWanted = wantToAdd;
			lastAdded = willAdd;

			return willAdd;
		}

		@Override
		public double getTotalToInvestmentPlan() {
			return toInvestPlan;
		}

		@Override
		public double getLastWantedToInvest() {
			double lastWanted = this.lastWanted;
			this.lastWanted = 0.0;
			return lastWanted;
		}

		@Override
		public double getLastAddedToInvest() {
			double lastAdded = this.lastAdded;
			this.lastAdded = 0.0;
			return lastAdded;
		}
	}

	private static class MakeReportCmd
		implements ICommandConstruction
	{
		@Override
		public void execute(IExecutionContext context) {
		}
	}

	// ### ### ### ### ### ### AGENTS  ### ### ### ### ### ### ###

	private static class ExistsPercpectiveInvestment
		extends BaseNegotiationAgent
		implements INegotiationCondition
	{
		private double truthDegree;
		private double previouslyWantedToInvest = Double.MAX_VALUE;

		public ExistsPercpectiveInvestment(double truthDegree) {
			this.truthDegree = truthDegree;
		}

		@Override
		public List<INegotiatingAgent> getAgents() {
			return Arrays.<INegotiatingAgent>asList(this);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			double wantToAddToInvestPlan = computeBestInvestGoal();
			wantToAddToInvestPlan *= truthDegree;

			double alreadyPlannedInvest =
				getPlannedFromBarterAndCash(context);
			wantToAddToInvestPlan -= alreadyPlannedInvest;

			double progress =
				previouslyWantedToInvest - wantToAddToInvestPlan;
			previouslyWantedToInvest = wantToAddToInvestPlan;
			if (progress < EPS) {
				return false;
			}

			// XXX: make service system to return array of implementors
			ICashInvestor cashInvestor =
				context.getService(ICashInvestor.class);
			double added =
				cashInvestor.addToInvestPlan(wantToAddToInvestPlan);
			wantToAddToInvestPlan -= added;

			IBarterInvestor barterInvestor =
				context.getService(IBarterInvestor.class);
			barterInvestor.addToInvestPlan(wantToAddToInvestPlan);

			if (wantToAddToInvestPlan < EPS) {
				return false;
			}
			return true;
		}

		private static double getPlannedFromBarterAndCash(
			INegotiationContext context
		) {
			ICashInvestor cashInvestor =
				context.getService(ICashInvestor.class);
			double cashInv = cashInvestor.getTotalToInvestmentPlan();

			IBarterInvestor barterInvestor =
				context.getService(IBarterInvestor.class);
			double bartInv =
				barterInvestor.getTotalToInvestmentPlan();

			return cashInv + bartInv;
		}

		private static double computeBestInvestGoal() {
			return 100.0;
		}
	}

	private static class CacheIsNotAsMuchAsWant
		extends BaseNegotiationAgent
		implements INegotiationCondition
	{
		protected CacheIsNotAsMuchAsWant(double truthDegree) {
		}

		@Override
		public List<INegotiatingAgent> getAgents() {
			return Arrays.<INegotiatingAgent>asList(this);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			ICashInvestor cashInvestor =
				context.getService(ICashInvestor.class);

			double lastWantedToInvest =
				cashInvestor.getLastWantedToInvest();
			double lastAddedToInvest =
				cashInvestor.getLastAddedToInvest();

			double dificitToInvest =
				lastWantedToInvest - lastAddedToInvest;
			if (dificitToInvest < EPS) {
				return false;
			}

			// TODOLONG: Exist___ only sends to BIGInvestor and it asks Barter and Cash Investors
			IBarterInvestor barterInvestor =
				context.getService(IBarterInvestor.class);
			barterInvestor.setCashIsInDificit(dificitToInvest);

			return true;
		}
	}

	private static class ExistNotNecessaryGoods
		extends BaseNegotiationAgent
		implements INegotiationCondition
	{
		private double[] availableGoods =
			new double[] { 1, 2, 3, 4 };

		@Override
		public List<INegotiatingAgent> getAgents() {
			return Arrays.<INegotiatingAgent>asList(this);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			double[] notNecessaryGoods = searchAvailableGoods();

			double sum = 0.0;
			for (double gv : notNecessaryGoods) {
				sum += gv;
			}
			if (sum < EPS) {
				return false;
			}

			IBarterInvestor barterInvestor =
				context.getService(IBarterInvestor.class);
			if (!barterInvestor.wantGoodsToSell()) {
				return false;
			}
			barterInvestor.
				onNotNecessaryGoodsAppeared(notNecessaryGoods);

			return true;
		}

		private double[] searchAvailableGoods() {
			return availableGoods;
		}
	}
}
